依赖注入是 Angular 自动处理对象的创建过程。
任何一个 Angular 程序都是组件、指令和一堆彼此依赖的类的集合。虽然每个组件内部都可以自己实现实例化的过程,但 Angular 内部引入依赖注入机制来完成这种工作。
依赖注入是一种设计模式。
依赖注入模式
依赖注入,Dependency Injection,简称 DI。
考虑一种情景。我们写了一个方法,方法的参数是一个对象,当我们调用这个方法时,需要实例化这个对象并传递给方法。
比如在下面这个方法中,product 是一个商品对象,createShipment 需要传入商品信息,也就是 product 实例对象。
1 | var product = new Product(); |
换句话说,createShipment 「依赖」 product 这个类,但是 createShipment 本身并不知道如何创建一个 product 实例。调用 createShipment() 方法需要以某种方式创建 product,并将其作为函数的参数传递给这个方法,也可以称为「注入」给该方法。
如果我们需要修改 product 的构造函数,只需要将代码做一些小的改动,如下:
1 | var product = new MockProduct(); |
但设想如果 createShipement 有三个参数,每个参数都是一个实例,而每个参数对象又有自己的依赖,那么代码的关系可能会变成:
1 | var product = new Product(); |
在上面的逻辑中,我们需要手工实例化多个对象,非常麻烦。
能否有一种工具,只需要我们写 createShipment(product, shipCompany, order);
,而工具可以替我们创建 createShipment 依赖的对象,以及这些对象各自依赖的对象呢?
这正是依赖注入帮我们解决的问题!
控制反转
控制反转,Inversion of Control,简称 IOC。
控制反转是指依赖的控制权从代码的内部转为代码的外部,以上边的例子为例:
依赖注入是实现控制反转的目的。
控制反转的实现手段是依赖注入。
依赖注入的好处
- 松耦合和可重用性
- 可测性更高
松耦合和可重用性
假设我们有一个商品组件 ProductComponent,需要使用商品服务 ProductService 获取商品服务,如果没有依赖注入,我们需要知道如何实例化这个服务。
有很多方法可以实现,比如使用 new 运算符、调用一个单例模式的 getInstance 方法、调用工厂模式的 createProduct 方法等:
1 | var productService = new ProductService(); |
但无论用哪种方式,ProductComponenent 都将于 ProductService 紧密地耦合在一起。
如果想在另一个项目中重用 ProductComponent,但是使用不同的服务对象获取商品信息, 必须把下面的代码
product.component.ts
1 | var productService = new ProductService(); |
改成:
product.component.ts
1 | var productService = new AnotherProductService(); |
用另一个商品服务对象获取商品服务信息,这说明 ProductComponent 与 ProductService 是紧密耦合在一起的。
如果想在别的项目中使用 ProductComponent,必须进行修改才行。而依赖注入帮助你解除 ProductComponent 与 ProductService 的紧耦合关系,从而在项目中可以重用 ProductComponent 而不用修改组件代码。
下面举个栗子:
app.module.ts
1 |
|
product.component.ts
1 | @Coponent({ |
声明:
使用 providers 声明。
下面两段代码是等价的:
1 | providers: [ProductService] |
这是一个 Token,一个 Token 代表一个可注入对象的类型。Token 的类型由 provider 配置对象的 provide 属性来决定。
上面这段代码表示注册一个类型是 ProductService(provide 字段) 的 Token,当组件或指令声明自己需要一个类型是 ProductService 的 token 时,实例化一个 ProductService(useClass 字段),并将其注入目标对象。
使用:
在组件的构造函数中声明使用。
1 | constructor(productService: ProductService){ |
上面的代码代表需要一个 ProductService 类型的 Token,看到这个声明后,会在 providers 中去找 ProductService (provide 字段)对应的类是哪个(useClass 字段)。
而组件本身并不知道传入的是哪个实现,更不需要明确地去实现实例化,只需要调用 Angular 为其创建好的对象即可。
如果希望在其他项目中使用 ProductService 类,那么只需要在那个项目中修改 app.module.ts 中的 provider 声明,组件本身并不需要修改。
1 | providers: [{provide: ProductService, useClass: AnotherProductService}] |
可测性
当真实的对象还没有被创建好时,可以添加一个虚拟的对象作为测试数据。
例如:LoginComponent 需要一个服务 LoginService,你可以先创建一个 MockLoginService。等认证服务器开发好之前,都可以注入 MockLoginService,开发完成后,只需要修改服务的引用即可。